package com.telit.common.exception;

import com.aliyuncs.exceptions.ClientException;
import com.telit.common.annotation.ResponseErrorType;
import com.telit.common.enums.ResponseProduceTypeEnum;
import com.telit.common.enums.ResultCode;
import com.telit.common.util.LanguageUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.telit.common.entity.DataReturn;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.Method;
import java.util.stream.Collectors;


/**
 * 自定义异常处理类
 */
@Slf4j
@RestControllerAdvice
public class ForestBaseExceptionHandler {


    private static ImmutableMap<Class<? extends Throwable>, ResultCode> EXCEPTIONS;
    protected static ImmutableMap.Builder<Class<? extends Throwable>, ResultCode> EXCEPTIONS_BUILDER = ImmutableMap.builder();
    private static ImmutableSet<Integer> IGNORE_CODE;
    protected static ImmutableSet.Builder<Integer> IGNORE_CODE_BUILDER = ImmutableSet.builder();

    static {
        EXCEPTIONS = EXCEPTIONS_BUILDER.build();

    }

    @ExceptionHandler(ForestBaseException.class)
    public Object handleForestBaseException(ForestBaseException e, HttpServletRequest request) {
        String message = e.getDefaultMessage();
        log.error(message, e);
        return getResponseObject(e, e.getStatus(), message, getType(), null);
    }

    /**
     * 系统异常
     */
//    @ExceptionHandler(SystemException.class)
//    public Object handleSystemException(SystemException e) {
//        ResultCode resultCode = EXCEPTIONS.get(e.getClass());
//        if (resultCode == null) {
//            resultCode = ResultCode.UNKNOWN_EXCEPTION_STATUS;
//        }
//        String message = LanguageUtils.codeMessage(resultCode.getCode());
//        if (StringUtils.isBlank(message)) {
//            message = e.getMessage();
//        }
//        if (StringUtils.isBlank(message)) {
//            message = "Unknown system exception";
//        }
//        log.error(message, e);
//        return getResponseObject(e, resultCode.getCode(), message, getType(), null);
//    }

    /**
     * 系统异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public Object handleIllegalArgumentException(IllegalArgumentException e) {
        log.error(e.getMessage(), e);
        return getResponseObject(e, ResultCode.REQUEST_PARAMS_CHECK_ERROR.getCode(),
                LanguageUtils.codeMessage(ResultCode.REQUEST_PARAMS_CHECK_ERROR.getCode(), e.getMessage()), getType(),
                null);
    }

    /**
     * 参数异常处理,get请求中@Valid失败抛出异常
     */
    @ExceptionHandler(BindException.class)
    public Object handleBindException(BindException e) {
        log.error(e.getMessage(), e);
        String message = e.getBindingResult().getAllErrors().stream().map(
                DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(","));
        return DataReturn.failure(ResultCode.REQUEST_PARAMS_CHECK_ERROR.getCode(),
                LanguageUtils.codeMessage(ResultCode.REQUEST_PARAMS_CHECK_ERROR.getCode(), message));
    }

    /**
     * 处理请求参数格式错误 @RequestParam上validated失败后抛出的异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Object handleConstraintViolationException(ConstraintViolationException e) {
        String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(
                Collectors.joining(","));
        log.error(message, e);
        return DataReturn.failure(ResultCode.REQUEST_PARAMS_CHECK_ERROR.getCode(),
                LanguageUtils.codeMessage(ResultCode.REQUEST_PARAMS_CHECK_ERROR.getCode(), message));
    }

    /**
     * 参数校验,处理@RequestBody上的Validated抛出的异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(
                DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(","));
        log.error(message, e);
        return DataReturn.failure(ResultCode.FORM_PARAMS_CHECK_ERROR.getCode(),
                LanguageUtils.codeMessage(ResultCode.FORM_PARAMS_CHECK_ERROR.getCode(), message));
    }

    @ExceptionHandler(ClientException.class)
    public Object handleClientException(ClientException e) {
        String message = e.getMessage().substring(e.getMessage().indexOf(":") + 1, e.getMessage().length());
        log.error(message, e);
        return DataReturn.failure(ResultCode.OPERATE_ERROR.getCode(),
                LanguageUtils.codeMessage(ResultCode.OPERATE_ERROR.getCode(), message));
    }

    /**
     * 其他异常
     */
    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e) {
        ResultCode resultCode = EXCEPTIONS.get(e.getClass());
        if (resultCode == null) {
            resultCode = ResultCode.UNKNOWN_EXCEPTION_STATUS;
        }
        String message = LanguageUtils.codeMessage(resultCode.getCode());
        if (StringUtils.isBlank(message)) {
            message = e.getMessage();
        }
        if (StringUtils.isBlank(message)) {
            message = "Unknown exception";
        }
        log.error(message, e);
        return getResponseObject(e, resultCode.getCode(), message, getType(), null);

    }

    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public DataReturn handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.error(e.getMessage(), e);
        return DataReturn.failure(ResultCode.REQUEST_PARAMS_CHECK_ERROR,
                LanguageUtils.codeMessage(ResultCode.REQUEST_PARAMS_CHECK_ERROR.getCode(), e.getParameterName()));
    }

    private Object getResponseObject(Exception e, String httpStatus, String message, ResponseProduceTypeEnum type,
                                     String pagePath) {
        // 指定返回类型
        if (type != null) {
            // 返回html
            if (ResponseProduceTypeEnum.HTML.equals(type)) {
                String path = getPath();
                // 若无指定返回页面，返回默认页面
                return StringUtils.isEmpty(path) ? returnDefaultHtml(httpStatus, message, "error") : returnHtml(path);
            }

            // json
            if (ResponseProduceTypeEnum.JSON.equals(type)) {
                return returnJson(httpStatus, message);
            }
        }
        return returnJson(httpStatus, message);
    }

    private DataReturn returnJson(String httpStatus, String message) {
        return DataReturn.failure(httpStatus, message);
    }

    /**
     * 返回默认的试图
     */
    private ModelAndView returnDefaultHtml(String status, String message, String pagePath) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("message", message);
        modelAndView.addObject("url", request.getRequestURL());
        modelAndView.addObject("status", status);
        modelAndView.setViewName("error");
        return modelAndView;
    }

    /**
     * 获取异常返回数据的type
     */
    private ResponseProduceTypeEnum getType() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //获取请求handler
        HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(
                "org.springframework.web.servlet.HandlerMapping.bestMatchingHandler");
        if (handlerMethod != null) {
            // 获取异常 Controller
            Class<?> beanType = handlerMethod.getBeanType();
            // 获取异常方法
            Method method = handlerMethod.getMethod();

            // 判断方法是否存在 ResponseErrorType 注解
            ResponseErrorType methodAnnotation = method.getAnnotation(ResponseErrorType.class);
            if (methodAnnotation != null) {
                // 是否使用异常处理
                if (methodAnnotation.type() != null) {
                    return methodAnnotation.type();
                }
            }
            // 判类是否存在 ResponseErrorType 注解
            ResponseErrorType classAnnotation = beanType.getAnnotation(ResponseErrorType.class);
            if (classAnnotation != null) {
                if (classAnnotation.type() != null) {
                    return classAnnotation.type();
                }
            }
        }
        return null;
    }

    private String getPath() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(
                "org.springframework.web.servlet.HandlerMapping.bestMatchingHandler");
        if (handlerMethod != null) {
            Class<?> beanType = handlerMethod.getBeanType();
            Method method = handlerMethod.getMethod();
            ResponseErrorType methodAnnotation = method.getAnnotation(ResponseErrorType.class);
            if (methodAnnotation != null && !StringUtils.isBlank(methodAnnotation.path())) {
                return methodAnnotation.path();
            }

            ResponseErrorType classAnnotation = beanType.getAnnotation(ResponseErrorType.class);
            if (classAnnotation != null && !StringUtils.isBlank(classAnnotation.path())) {
                return classAnnotation.path();
            }
        }

        return null;
    }

    /**
     * 返回指定的报错图片
     */
    private ModelAndView returnHtml(String path) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName(path);
        return modelAndView;
    }

}
